﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using System.Drawing;

namespace gpsmid_wince.util {
	class SingleTile : Tile {

		public const sbyte STATE_NOTLOAD = 0;

		public const sbyte STATE_LOADSTARTED = 1;

		public const sbyte STATE_LOADREADY = 2;

		private const sbyte STATE_CLEANUP = 3;

		private Logger logger = Logger.getInstance("SingleTile", Logger.DEBUG);

		public short[] nodeLat;

		public short[] nodeLon;

		public int[] nameIdx;

		public int[] urlIdx;

		public int[] phoneIdx;

		public sbyte[] type;

		private Way[][] ways;

		internal sbyte state = 0;

		bool abortPainting = false;

		public sbyte zl;

		private Color col;
		public Color getColor() { return col; }
		private static Color[] cols = {
			Color.Green, Color.Blue, Color.Red, 
			Color.Pink, Color.Cyan, Color.Yellow, 
			Color.Turquoise, Color.Violet, Color.SkyBlue,
			Color.Lime, Color.MediumOrchid, Color.Orange };
		private static Random rnd = new Random(0);

		public SingleTile(JavaDataInputStream dis, int deep, sbyte zl) {
			this.zl = zl;
			minLat = dis.readFloat();
			minLon = dis.readFloat();
			maxLat = dis.readFloat();
			maxLon = dis.readFloat();
			fileId = (short)dis.readInt();

			col = cols[rnd.Next(cols.Length)];

			isDataReady();
		}

		private bool isDataReady() {
			if (state == STATE_NOTLOAD) {
				// logger.debug("singleTile start load " + fileId );
				state = STATE_LOADSTARTED;
				//Trace.getInstance().getDataReader().add(this,this);
				readData(null);
				return false;
			}
			if (state == STATE_LOADSTARTED) {
				// logger.debug("singleTile wait for load " + fileId);
				// drawBounds(pc, 255, 255, 55);
				return false;
			}
			if (state == STATE_CLEANUP) {
				// logger.debug("singleTile wait for Cleanup " + fileId);			
				return false;
			}
			return true;

		}

		public override void walk(PaintContext pc, int opt) {
			walk(pc, opt, Tile.LAYER_ALL);
		}

		public override void paint(PaintContext pc, sbyte layer) {
			walk(pc, Tile.OPT_PAINT, layer);
		}

		public float getAbsLat(short lat) {
			return lat/Projection.PLANET_RAD_M + centerLat;
		}
		public float getAbsLon(short lon) {
			return lon/Projection.PLANET_RAD_M + centerLon;
		}

		private Pen pen = null;
		public static int countDrawn;
		private Object WALK_LOCK = new Object();
		private void walk(PaintContext pc, int opt, sbyte layer) {
			if (pen == null) pen = new Pen(col);
			lock (WALK_LOCK) {
				if (contain(pc)) {
					while (!isDataReady())
						if ((opt & Tile.OPT_WAIT_FOR_LOAD) == 0) {
							logger.debug("Walk don't wait for TileData");
							return;
						} else {
							lock (this) {
								Thread.Sleep(1000);
								logger.debug("Walk Wait for TileData");
							}
						}
					//logger.info("drawing tile");
					countDrawn++;

					Point pt = pc.p.toWin(minLat, minLon);
					Point p2 = pc.p.toWin(maxLat, maxLon);
					System.Drawing.Rectangle rr = new System.Drawing.Rectangle(pt.X, p2.Y, p2.X - pt.X, pt.Y - p2.Y);
					//logger.debug("Tile area: " + rr);
					//rr.Inflate(1,1);
					pc.g.DrawRectangle(pen, rr);
					if (layer != Tile.LAYER_NODE) {
						//render ways
						Way[][] fways = getWays();
						foreach (Way[] ways in fways) if (ways != null) {
								foreach (Way way in ways) {
									if (way.isOnScreen(pc, this))
										way.processPath(pc, this, opt, layer);
								}
							}
					}

				} // else logger.info("not drawing tile " + this);
			}
		}



		public override bool cleanup(int level) {
			if (state != STATE_NOTLOAD) {
				// logger.info("test tile unused fid:" + fileId + "c:"+lastUse);
				if (lastUse > level) {
					abortPainting = true;
					lock (this) {
						// nodes = null;
						state = STATE_CLEANUP;
						nameIdx = null;
						urlIdx = null;
						phoneIdx = null;
						nodeLat = null;
						nodeLon = null;
						type = null;
						setWays(null);
						state = STATE_NOTLOAD;
					}
					abortPainting = false;
					// logger.info("discard content for tile " + fileId);
					return true;
				}
			}
			return false;
		}

		public void setWays(Way[][] ways) {
			this.ways = ways;
		}

		public Way[][] getWays() {
			return ways;
		}

		public void readData(Object notifyReady) {
			SingleTile tt = this;
			Stream is_ = File.OpenRead(DictReader.basename + @"/t" + tt.zl + tt.fileId + ".d");
			if (is_ == null) {
				//#debug error
				logger.error("File inputStream/t" + tt.zl + tt.fileId + ".d not found");
				tt.state = 0;
				return;
			}
			JavaDataInputStream ds = new JavaDataInputStream(is_);

			//		logger.info("read Magic code");
			if (ds.readByte() != 0x54) {
				throwError("Not a MapMid-file", tt);
			}
			tt.centerLat = ds.readFloat();
			tt.centerLon = ds.readFloat();
			//logger.debug("Center coordinate of tile: " + tt.centerLat + "/" + tt.centerLon);
			int nodeCount = ds.readShort();
			short[] radlat = new short[nodeCount];
			short[] radlon = new short[nodeCount];
			int iNodeCount = ds.readShort();
			logger.trace("nodes total: " + nodeCount + " interestNode: " + iNodeCount);
			int[] nameIdx = new int[iNodeCount];
			for (int i = 0; i < iNodeCount; i++) {
				nameIdx[i] = -1;
			}
			sbyte[] type = new sbyte[iNodeCount];
			int[] osmID;
			//#if polish.api.osm-editing
			if (Legend.enableEdits) {
				osmID = new int[iNodeCount];
			} else {
				osmID = null;
			}
			//#else
			osmID = null;
			//#endif
			sbyte flag = 0;
			//#debug trace
			logger.trace("About to read nodes");
			try {
				for (int i = 0; i < nodeCount; i++) {
					//#debug error
					//	logger.info("read coord :"+i+"("+nodeCount+")");

					flag = ds.readByte();

					radlat[i] = ds.readShort();
					radlon[i] = ds.readShort();

					if ((flag & Legend.NODE_MASK_NAME) > 0) {
						if ((flag & Legend.NODE_MASK_NAMEHIGH) > 0) {
							nameIdx[i] = ds.readInt();
						} else {
							nameIdx[i] = ds.readShort();
						}
					}
					if ((flag & Legend.NODE_MASK_TYPE) > 0) {
						type[i] = ds.readByte();
						//#if polish.api.osm-editing
						if (Legend.enableEdits) {
							osmID[i] = ds.readInt();
						}
						//#endif

					}
				}
				tt.nameIdx = nameIdx;
				tt.nodeLat = radlat;
				tt.nodeLon = radlon;
				tt.type = type;
			} catch (Exception e) {
				throwError("Reading nodes: "+e.Message, tt);
			}
			//logger.info("read nodes");
			if (ds.readByte() != 0x55) {
				logger.error("Reading nodes went wrong / start of ways not found");
				throwError("Nodes not OK", tt);
			}
			int wayCount = ds.readShort();
			//		logger.trace("reading " + wayCount + " ways");
			int lastread = 0;
			try {
				Way[] tmpWays = new Way[wayCount];
				sbyte[] layers = new sbyte[wayCount];
				short[] layerCount = new short[5];
				for (int i = 0; i < wayCount; i++) {
					sbyte flags = ds.readByte();
					if (/*flags != 128*/true) {
						Way w;
						//#if polish.api.osm-editing
						//#else
						w = new Way(ds, flags, tt, layers, i);
						w.wayNrInFile = (short)i;
						//#endif
						tmpWays[i] = w;
						/**
						 * To save resources, only allow layers -2 .. +2
						 */
						if (layers[i] < -2) {
							layers[i] = -2;
						} else if (layers[i] > 2) {
							layers[i] = 2;
						}
						layers[i] += 2;
						layerCount[layers[i]]++;
					}
					lastread = i;
				}

				/**
				 * Split way list into different layers
				 */
				tt.ways = new Way[5][];
				for (int i = 0; i < 5; i++) {
					if (layerCount[i] > 0) {
						tt.ways[i] = new Way[layerCount[i]];
						int k = 0;
						for (int j = 0; j < wayCount; j++) {
							if (layers[j] == i) {
								tt.ways[i][k++] = tmpWays[j];
							}
						}
					}
				}
			} catch (Exception e) {
				throwError("Ways (last ok index " + lastread + " out of " + wayCount + "): "+e.Message, tt);
			}
			if (ds.readByte() != 0x56) {
				throwError("Ways not OK, failed to read final magic value", tt);
			} 
			ds.close();

			//tt.dataReady();
			//tt.isDataReady();
			tt.state = STATE_LOADREADY;
			//trace.newDataReady();
			/*
			if (notifyReady != null) {
				lock (notifyReady) {
					Monitor.PulseAll(notifyReady);
					//notifyReady.notifyAll();
				}
			}*/
		}


		private void throwError(string msg, Object obj) {
			throw new IOException(msg);
		}
	}
}
